<template>
  <article class="page page-img-similarity">
    <h4 class="text-center">图片相似度计算</h4>
    <form class="form">
      <div class="form-group">
        <fieldset>
          <legend>参数设置</legend>
          <div class="row-flex" align="center">
            <div class="row-flex-prefix">标的图片</div>
            <div class="row-flex-main">
                <input class="form-control" type="text" v-model.trim="referPath" placeholder="请选择标的图片" readonly />
            </div>
            <div class="row-flex-suffix"><x-button @click="setReferPath">选择...</x-button></div>
          </div>
          <div class="row-flex" align="center">
            <div class="row-flex-prefix">指纹算法</div>
            <div class="row-flex-main text-left">
              <div class="form-check form-check-inline" v-for="(v,i) in fingerprintList" :key="i">
                <input class="form-check-input" type="checkbox" :value="v.value" v-model="fingerprint">
                <label class="form-check-label">{{indexDict[v.value]}} {{v.label}}</label>
              </div>
            </div>
          </div>
          <div class="row-flex" align="center">
            <div class="row-flex-prefix">比对算法</div>
            <div class="row-flex-main text-left">
              <div class="form-check form-check-inline" v-for="(v,i) in compareList" :key="i">
                <input class="form-check-input" type="checkbox" :value="v.value" v-model="compare">
                <label class="form-check-label">{{indexDict[v.value]}} {{v.label}}</label>
              </div>
            </div>
          </div>
        </fieldset>
      </div>
      <div class="form-group form-group-main row">
        <div class="col col-9">
            <fieldset>
                <legend>图片列表</legend>
                <div class="fieldset-content">
                  <div class="table-header" :style="thStyles">
                    <table>
                      <tbody>
                        <tr>
                          <th v-for="(v,i) in tableHeaders" :key="`th_${i}`" :style="{width: v.width}">{{v.label}}</th>
                        </tr>
                      </tbody>
                    </table>
                  </div>
                  <div class="table-body" @drop.prevent="filesDrop" @dragover.prevent>
                    <table ref="table">
                      <tbody>
                        <tr v-for="(v,i) in imagesList" :key="`file_${i}`" :class="{active: i === imageActiveIndex}" @click="clickImageItem(i)">
                          <td v-for="(item,index) in tableHeaders" :key="`td_${i}_${index}`" :style="{width: item.width}">
                            <div v-if="item.value==='fingerprint'" class="text-left td-content">
                              <div v-for="(v1,i1) in myFingerprint" :key="`div_${i}_${index}_${i1}`" class="text-ellipsis" :title="v.fingerprint[v1]">
                                {{`${indexDict[v1]} ${v.fingerprint[v1] || '--'}`}}
                              </div>
                            </div>
                            <div v-else-if="item.value==='compare'" class="text-left td-content">
                              <div v-for="(v1,i1) in myFingerprint" :key="`div_${i}_${index}_${i1}`" class="text-ellipsis">
                                <!-- {{`① ${v.compare[v1][0] || '--'} ② ${v.compare[v1][1] || '--'}`}} -->
                                <span :class="getColorClass(v.compare[v1][0])">① {{ v.compare[v1][0] || '--' }}</span>
                                <span :class="getColorClass(v.compare[v1][1])" style="margin-left: .5em;">② {{ v.compare[v1][1] || '--' }}</span>
                              </div>
                            </div>
                            <span v-else>{{v[item.value] || ''}}</span>
                          </td>
                        </tr>
                        <tr v-if="imagesList.length===0" class="nodata-tr"><td>将待处理的文件拖动到此处</td></tr>
                      </tbody>
                    </table>
                  </div>
                </div>
            </fieldset>
        </div>
        <div class="col col-3">
            <fieldset>
                <legend>图片预览</legend>
                <div class="fieldset-content preview">
                    <img class="preview-img" :src="imagePreviewInfo.src || imagePreviewInfo.path" />
                    <img class="preview-img-8" :src="imagePreviewInfo.imgUrl_8" v-if="imagePreviewInfo.imgUrl_8" />
                    <div class="preview-name text-ellipsis">{{imagePreviewInfo.name}}</div>
                </div>
            </fieldset>
        </div>
      </div>
      <div class="form-group text-center">
        <x-button type="default" @click="clearImagesList">清空列表</x-button>
        <x-button type="primary" @click="getFingerprintAll" :loading="loadingCount > 0">开始计算</x-button>
      </div>
      <div v-if="loadingCount > 0" class="mask">
        <x-loading></x-loading>
      </div>
    </form>
  </article>
</template>

<script>
const fs = global.require('fs')
const {remote} = global.require('electron')
const {dialog} = remote
const selfWindow= remote.getCurrentWindow()
let similarity = require('./similarity.js')

export default {
  name: 'img-similarity',
  data () {
    return {
        // 标的图片
        referPath: '',
        referInfo: {
          name: '标的图片', path: '', src: '', imgObj: null, imgData_8: null, imgUrl_8: '', imgData_32: null,
          fingerprint: ['', '', '', ''], compare: [['', ''], ['', ''], ['', ''], ['', '']]
        },
        fingerprint: ['0', '1', '2', '3'],
        fingerprintList: [
          {label: '平均哈希算法', value: '0'},
          {label: '感知哈希算法', value: '1'},
          {label: '颜色分布法', value: '2'},
          {label: '内容特征法', value: '3'},
        ],
        compare: ['0', '1'],
        compareList: [
          {label: '汉明距离', value: '0'},
          {label: '余弦相似度', value: '1'},
        ],
        imagesList: [],
        imageActiveIndex: -1,
        tableHeaders: [
          {label: '文件名', value: 'name', width: '20%'},
          {label: '指纹', value: 'fingerprint', width: '60%'},
          {label: '相似度', value: 'compare', width: '20%'},
        ],
        thStyles: {},
        indexDict: {0: '①', 1: '②', 2: '③', 3: '④'},
        loadingCount: 0
    }
  },
  computed: {
    myFingerprint () {
      let result = [...this.fingerprint].sort()
      return result
    },
    myCompare () {
      let result = [...this.compare].sort()
      return result
    },
    imagePreviewInfo () {
        let imageActive = this.imagesList[this.imageActiveIndex]
        return imageActive || this.referInfo
    }
  },
  methods: {
    // 设置标的图片位置
    setReferPath () {
      let filePaths = dialog.showOpenDialogSync(selfWindow, {
        title: '选择文件',
        buttonLabel:'确定',
        defaultPath: this.$sharedObject('desktopPath'),
        properties: ['openFile'],
        filters: [ { name: '图片', extensions: ['png', 'jpg', 'jpeg', 'gif'] } ]
      })
      if (filePaths instanceof Array && filePaths.length > 0) {
        this.referPath = filePaths[0]
        this.resetImagesList()
        this.getReferInfo()
      }
    },
    // 重置imagesList数据
    resetImagesList () {
      this.imagesList = this.imagesList.map(v => {
        return {...v, compare: [['', ''], ['', ''], ['', ''], ['', '']]}
      })
    },
    // 获取标的图片信息
    getReferInfo () {
      this.referInfo = {
        name: '标的图片', path: this.referPath, src: '', imgObj: null, imgData_8: null, imgUrl_8: '', imgData_32: null,
        fingerprint: ['', '', '', ''], compare: [['', ''], ['', ''], ['', ''], ['', '']]
      }
      if (!this.referPath || !fs.existsSync(this.referPath)) return
      fs.readFile(this.referPath, (err, data) => {
        if (err || !data) {
          return this.$toast('获取标的图片信息发生错误')
        }
        this.referInfo.src = `data:image/png;base64,${data.toString('base64')}`
        this.loadingCount++
        this.$jstools.loadImage(this.referInfo.src).then((imgObj) => {
          let imgData_8 = similarity.compressImg2(imgObj, 8)
          let imgData_32 = similarity.compressImg2(imgObj, 32)
          let grayImgData = similarity.createGrayscale(imgData_8)
          let fp0 = similarity.getHashFingerprint(grayImgData)
          let fp1 = similarity.getPHashFingerprint(imgData_32)
          let simplifiedDataList = similarity.simplifyColorData(imgData_32)
          let zonedList = similarity.seperateListToColorZone(simplifiedDataList)
          let fp2 = similarity.getColorFingerprint(zonedList)
          let threshold = similarity.OTSUAlgorithm(imgData_32)
          let fp3 = similarity.getOTSUFingerprint(imgData_32, threshold)
          this.referInfo.imgObj = imgObj
          // this.referInfo.imgData_8 = grayImgData
          this.referInfo.imgUrl_8 = similarity.imgData2imgUrl(grayImgData, 8)
          this.referInfo.fingerprint = [fp0, fp1, fp2, fp3]
        }).finally(() => {
          this.loadingCount--
        })
      })
    },
    // 拖拽添加文件
    filesDrop (e) {
      const fileList = e.dataTransfer.files
      if (!fileList) return
      let taskList = []
      let indexList = []
      for (let i = 0; i < fileList.length; i++) {
        const file = fileList[i]
        if (!file.type || !/.(gif|jpg|jpeg|png)$/i.test(file.name)) {
          continue
        }
        taskList.push(this.$jstools.loadImage(file))
        indexList.push(i)
      }
      this.loadingCount++
      Promise.all(taskList).then((imgList) => {
        imgList.forEach((v, i) => {
          let file = fileList[indexList[i]]
          this.imagesList.push({
            name: file.name, path: file.path, src: v.src, imgObj: v || null, imgData_8: null, imgUrl_8: '', imgData_32: null,
            fingerprint: ['', '', '', ''], compare: [['', ''], ['', ''], ['', ''], ['', '']]
          })
        })
        this.checkScroll()
      }).finally(() => {
        this.loadingCount--
      })
    },
    // 点击图片
    clickImageItem (index) {
      if (this.imageActiveIndex === index) {
        this.imageActiveIndex = -1
      } else {
        this.imageActiveIndex = index
      }
    },
    // 清空列表
    clearImagesList () {
      this.imagesList = []
      this.imageActiveIndex = -1
      this.thStyles = {}
    },
    // 获取图片指纹
    getFingerprintAll () {
      if (this.imagesList.length <= 0) {
        return this.$toast('未找到可处理的文件')
      }
      if (this.fingerprint.length <= 0) {
        return this.$toast('指纹算法不能为空')
      }
      if (!this.referPath) {
        return this.$toast('请先选择标的图片')
      }
      this.loadingCount++
      setTimeout(() => {
        let taskList = []
        this.imagesList.forEach((v, i) => {
          taskList.push(this.getFingerprintItem(v, i))
        })
        Promise.all(taskList).finally(() => {
          this.loadingCount--
          this.$toast('处理完成')
          this.$forceUpdate()
        })
      }, 100)
    },
    getFingerprintItem (item, index) {
      if (!item || !item.imgObj) return Promise.reject('无效数据')
      item = {...item}
      this.fingerprint.forEach(v => {
        let fp = ''
        if (!v || item.fingerprint[v]) return
        if (v === '0') {
          let imgData = similarity.compressImg2(item.imgObj, 8)
          let grayImgData = similarity.createGrayscale(imgData)
          // this.imagesList[index].imgData_8 = grayImgData
          this.imagesList[index].imgUrl_8 = similarity.imgData2imgUrl(grayImgData, 8)
          fp = similarity.getHashFingerprint(grayImgData)
        } else if (v === '1') {
          let imgData = this.imagesList[index].imgData_32
          if (!imgData) {imgData = this.imagesList[index].imgData_32 = similarity.compressImg2(item.imgObj, 32)}
          fp = similarity.getPHashFingerprint(imgData)
        } else if (v === '2') {
          let imgData = this.imagesList[index].imgData_32
          if (!imgData) {imgData = this.imagesList[index].imgData_32 = similarity.compressImg2(item.imgObj, 32)}
          let simplifiedDataList = similarity.simplifyColorData(imgData)
          let zonedList = similarity.seperateListToColorZone(simplifiedDataList)
          fp = similarity.getColorFingerprint(zonedList)
        } else if (v === '3') {
          let imgData = this.imagesList[index].imgData_32
          if (!imgData) {imgData = this.imagesList[index].imgData_32 = similarity.compressImg2(item.imgObj, 32)}
          let threshold = similarity.OTSUAlgorithm(imgData)
          fp = similarity.getOTSUFingerprint(imgData, threshold)
        }
        this.imagesList[index].fingerprint[v] = fp
      })
      this.compare.forEach(v => {
        this.fingerprint.forEach(v1 => {
          let val1 = this.referInfo.fingerprint[v1]
          let val2 = this.imagesList[index].fingerprint[v1]
          if (v === '0' && v1 !== '2') {
            this.imagesList[index].compare[v1][v] = similarity.hammingSimilarity(val1, val2)
          } else if (v === '1') {
            this.imagesList[index].compare[v1][v] = similarity.cosineSimilarity(val1, val2)
          }
        })
      })
      return Promise.resolve()
    },
    getColorClass (val) {
      let result = ''
      val = parseFloat(val) || 0
      if (val >= 0.8) { result = 'text-danger' }
      else if (val >= 0.6) { result = 'text-success' }
      return result
    },
    checkScroll () {
      this.$nextTick(() => {
        if (!this.$refs.table) return
        let tableHeight = this.$refs.table.clientHeight
        let tableParentHeight = this.$refs.table.parentNode.clientHeight
        if (tableHeight > tableParentHeight) {
          this.thStyles = {overflow: 'hidden scroll'}
        } else {
          this.thStyles = {overflow: 'hidden'}
        }
      })
    },
  }
}
</script>

<style scoped lang="less">
@import "~@/assets/css/_mixins.less";
@import "~@/assets/css/_color.less";
.row-flex {
  & + .row-flex { margin-top: .5em; }
  .row-flex-prefix { width: 4.5em; text-align: left; }
}
.form {
  height: calc(100vh - 137px);
  display: flex;
  flex-direction: column;
  position: relative;
  .form-group {
    fieldset {
        height: 100%;
        .fieldset-content {
            height: calc(100% - 2em);
            display: flex;
            flex-direction: column;

            .table-header,
            .table-body {
              table {
                width: 100%;
                border: 1px solid @borderGrey;
                tr {height: 2em;}
                th, td {
                  border: 1px solid @borderGrey;
                  text-align: center;
                  vertical-align: middle;
                }
                .nodata-tr {
                  height: 100%;
                  font-size: 16px;
                  color: #999;
                }
              }
            }
            .table-body {
              height: calc(100% - 1.5em);
              overflow: auto;
              table {
                min-height: 100%;
                tr {cursor: pointer;}
                tr.active { background-color: @bgGrey; }
                .td-content {
                  padding: .1em .5em;
                }
              }
            }
            .preview-img {
              display: block;
              width: 100%;
              height: 100%;
              object-fit: contain;
            }
            .preview-img-8 {
              position: absolute;
              top: 2px;
              right: 2px;
              width: 50px;
              height: 50px;
              border: 2px dashed @colorPrimary;
              object-fit: contain;
              background-color: rgba(255, 255, 255, .5);
            }
            .preview-name {
              position: absolute;
              bottom: 0;
              left: 0;
              width: 100%;
              height: 2em;
              line-height: 2;
              color: #fff;
              text-align: center;
              background-color: rgba(0, 0, 0, .3);
            }
        }
        .preview {
          border-radius: .5em;
          background-color: @bgGrey;
          overflow: hidden;
          position: relative;
        }
    }
  }
  .form-group-main {
    height: 0;
    flex-grow: 1;
    & > .col {height: 100%;}
  }
  .mask {
    .absolute-fullscreen;
    z-index: 2;
    border-radius: 8px;
    background-color: rgba(255,255,255,.5);
    .flex-center;
  }
}
</style>
